// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "mtk-sdmmc.h"

#include <atomic>

#include <fbl/auto_call.h>
#include <fbl/condition_variable.h>
#include <mock-mmio-reg/mock-mmio-reg.h>
#include <zxtest/zxtest.h>

namespace {

constexpr size_t kRegisterCount = 139;

}  // namespace

namespace sdmmc {

class MtkSdmmcTest : public MtkSdmmc, public ddk::InBandInterruptProtocol<MtkSdmmcTest> {
 public:
  MtkSdmmcTest(ddk_mock::MockMmioRegRegion& registers, bool is_sdio = false)
      : MtkSdmmc(nullptr, ddk::MmioBuffer(registers.GetMmioBuffer()), zx::bti(ZX_HANDLE_INVALID),
                 kNullHostInfo, zx::interrupt(ZX_HANDLE_INVALID), ddk::GpioProtocolClient(),
                 ddk::GpioProtocolClient(),
                 board_mt8167::MtkSdmmcConfig{
                     .fifo_depth = 128, .src_clk_freq = 200000000, .is_sdio = is_sdio}),
        interrupt_count_(0),
        callbacks_received_(0) {}

  MtkSdmmcTest(zx_device_t* parent, ddk_mock::MockMmioRegRegion& registers, bool is_sdio = false)
      : MtkSdmmc(parent, ddk::MmioBuffer(registers.GetMmioBuffer()), zx::bti(ZX_HANDLE_INVALID),
                 kNullHostInfo, zx::interrupt(ZX_HANDLE_INVALID), ddk::GpioProtocolClient(),
                 ddk::GpioProtocolClient(),
                 board_mt8167::MtkSdmmcConfig{
                     .fifo_depth = 128, .src_clk_freq = 200000000, .is_sdio = is_sdio}),
        interrupt_count_(0),
        callbacks_received_(0) {}

  zx_status_t RegisterInBandInterrupt() {
    in_band_interrupt_protocol_t callback{&in_band_interrupt_protocol_ops_, this};
    return MtkSdmmc::SdmmcRegisterInBandInterrupt(&callback);
  }

  void TriggerInterrupts(uint32_t count) {
    fbl::AutoLock mutex_al(&mutex_);
    interrupt_count_ += count;
  }

  void WaitForCallbacks(uint32_t count) {
    for (uint32_t i = 0; i < count; i += callbacks_received_.exchange(0)) {
      sync_completion_wait(&callback_completion_, ZX_TIME_INFINITE);
      sync_completion_reset(&callback_completion_);
    }
  }

  void StopIrqThread() {
    thread_stop_ = true;
    JoinIrqThread();
  }

  void InBandInterruptCallback() {
    callbacks_received_++;
    sync_completion_signal(&callback_completion_);
  }

 protected:
  zx_status_t WaitForInterrupt(zx::time* timestamp) override {
    while (!thread_stop_) {
      fbl::AutoLock mutex_al(&mutex_);
      if (interrupt_count_ > 0) {
        interrupt_count_--;
        return ZX_OK;
      } else if (req_ != nullptr) {
        return ZX_OK;
      }
    }

    return ZX_ERR_CANCELED;
  }

  volatile bool thread_stop_ = false;

 private:
  static constexpr sdmmc_host_info_t kNullHostInfo{
      .caps = 0, .max_transfer_size = 0, .max_transfer_size_non_dma = 0, .prefs = 0};

  sync_completion_t callback_completion_;
  uint32_t interrupt_count_ TA_GUARDED(mutex_);
  std::atomic<uint32_t> callbacks_received_;
};

template <class T>
ddk_mock::MockMmioReg& GetMockReg(ddk_mock::MockMmioRegRegion& registers) {
  return registers[T::Get().addr()];
}

template <class T>
ddk_mock::MockMmioReg& GetMockReg(int index, ddk_mock::MockMmioRegRegion& registers) {
  return registers[T::Get(index).addr()];
}

void VerifyAll(ddk_mock::MockMmioReg* registers) {
  for (size_t i = 0; i < kRegisterCount; i++) {
    registers[i].VerifyAndClear();
  }
}

TEST(SdmmcTest, SetBusWidth) {
  ddk_mock::MockMmioReg reg_array[kRegisterCount];
  ddk_mock::MockMmioRegRegion mock_regs(reg_array, sizeof(uint32_t), kRegisterCount);
  MtkSdmmcTest sdmmc(mock_regs);

  GetMockReg<SdcCfg>(mock_regs).ExpectWrite(SdcCfg().set_bus_width(SdcCfg::kBusWidth4).reg_value());
  EXPECT_OK(sdmmc.SdmmcSetBusWidth(SDMMC_BUS_WIDTH_FOUR));
  ASSERT_NO_FATAL_FAILURES(mock_regs.VerifyAll());

  GetMockReg<SdcCfg>(mock_regs).ExpectWrite(SdcCfg().set_bus_width(SdcCfg::kBusWidth1).reg_value());
  EXPECT_OK(sdmmc.SdmmcSetBusWidth(SDMMC_BUS_WIDTH_ONE));
  ASSERT_NO_FATAL_FAILURES(mock_regs.VerifyAll());

  GetMockReg<SdcCfg>(mock_regs).ExpectWrite(SdcCfg().set_bus_width(SdcCfg::kBusWidth8).reg_value());
  EXPECT_OK(sdmmc.SdmmcSetBusWidth(SDMMC_BUS_WIDTH_EIGHT));
  mock_regs.VerifyAll();
}

TEST(SdmmcTest, SetBusFreq) {
  ddk_mock::MockMmioReg reg_array[kRegisterCount];
  ddk_mock::MockMmioRegRegion mock_regs(reg_array, sizeof(uint32_t), kRegisterCount);
  MtkSdmmcTest sdmmc(mock_regs);

  // 400 kHz: Use divider value 125.
  auto msdc_cfg = MsdcCfg().set_card_ck_mode(MsdcCfg::kCardCkModeNoDiv);
  GetMockReg<MsdcCfg>(mock_regs)
      .ExpectRead(msdc_cfg.reg_value())
      .ExpectWrite()
      .ExpectWrite(
          msdc_cfg.set_card_ck_mode(MsdcCfg::kCardCkModeDiv).set_card_ck_div(125).reg_value())
      .ExpectRead(msdc_cfg.set_card_ck_stable(1).reg_value());

  EXPECT_OK(sdmmc.SdmmcSetBusFreq(400000));
  ASSERT_NO_FATAL_FAILURES(mock_regs.VerifyAll());

  // DDR 1 MHz: Use divider value 25.
  msdc_cfg.set_reg_value(0).set_card_ck_mode(MsdcCfg::kCardCkModeDdr);
  GetMockReg<MsdcCfg>(mock_regs)
      .ExpectRead(msdc_cfg.reg_value())
      .ExpectWrite()
      .ExpectWrite(msdc_cfg.set_card_ck_div(25).reg_value())
      .ExpectRead(msdc_cfg.set_card_ck_stable(1).reg_value());

  EXPECT_OK(sdmmc.SdmmcSetBusFreq(1000000));
  ASSERT_NO_FATAL_FAILURES(mock_regs.VerifyAll());

  // SDR 200 MHz: No divider.
  msdc_cfg.set_reg_value(0).set_card_ck_mode(MsdcCfg::kCardCkModeDiv).set_card_ck_div(50);
  GetMockReg<MsdcCfg>(mock_regs)
      .ExpectRead(msdc_cfg.reg_value())
      .ExpectWrite()
      .ExpectWrite(
          msdc_cfg.set_card_ck_mode(MsdcCfg::kCardCkModeNoDiv).set_card_ck_div(0).reg_value())
      .ExpectRead(msdc_cfg.set_card_ck_stable(1).reg_value());

  EXPECT_OK(sdmmc.SdmmcSetBusFreq(200000000));
  ASSERT_NO_FATAL_FAILURES(mock_regs.VerifyAll());

  // HS400 mode @ 200 MHz: No divider.
  msdc_cfg.set_reg_value(0).set_card_ck_mode(MsdcCfg::kCardCkModeHs400);
  GetMockReg<MsdcCfg>(mock_regs)
      .ExpectRead(msdc_cfg.reg_value())
      .ExpectWrite()
      .ExpectWrite(msdc_cfg.set_hs400_ck_mode(1).reg_value())
      .ExpectRead(msdc_cfg.set_card_ck_stable(1).reg_value());

  EXPECT_OK(sdmmc.SdmmcSetBusFreq(200000000));
  ASSERT_NO_FATAL_FAILURES(mock_regs.VerifyAll());

  // HS400 mode @ 10 MHz: Use divider value 3.
  msdc_cfg.set_reg_value(0).set_card_ck_mode(MsdcCfg::kCardCkModeHs400).set_hs400_ck_mode(1);
  GetMockReg<MsdcCfg>(mock_regs)
      .ExpectRead(msdc_cfg.reg_value())
      .ExpectWrite()
      .ExpectWrite(msdc_cfg.set_card_ck_div(3).set_hs400_ck_mode(0).reg_value())
      .ExpectRead(msdc_cfg.set_card_ck_stable(1).reg_value());

  EXPECT_OK(sdmmc.SdmmcSetBusFreq(10000000));
  mock_regs.VerifyAll();
}

TEST(SdmmcTest, SetTiming) {
  ddk_mock::MockMmioReg reg_array[kRegisterCount];
  ddk_mock::MockMmioRegRegion mock_regs(reg_array, sizeof(uint32_t), kRegisterCount);
  MtkSdmmcTest sdmmc(mock_regs);

  auto msdc_cfg = MsdcCfg().set_card_ck_mode(MsdcCfg::kCardCkModeDiv);
  GetMockReg<MsdcCfg>(mock_regs)
      .ExpectRead(msdc_cfg.reg_value())
      .ExpectWrite()
      .ExpectRead()
      .ExpectWrite(msdc_cfg.set_card_ck_mode(MsdcCfg::kCardCkModeDdr).reg_value())
      .ExpectRead(msdc_cfg.set_card_ck_stable(1).reg_value());

  EXPECT_OK(sdmmc.SdmmcSetTiming(SDMMC_TIMING_HSDDR));
  ASSERT_NO_FATAL_FAILURES(mock_regs.VerifyAll());

  msdc_cfg.set_reg_value(0).set_card_ck_mode(MsdcCfg::kCardCkModeDiv);
  GetMockReg<MsdcCfg>(mock_regs)
      .ExpectRead(msdc_cfg.reg_value())
      .ExpectWrite()
      .ExpectRead()
      .ExpectWrite(msdc_cfg.set_card_ck_mode(MsdcCfg::kCardCkModeHs400).reg_value())
      .ExpectRead(msdc_cfg.set_card_ck_stable(1).reg_value());

  EXPECT_OK(sdmmc.SdmmcSetTiming(SDMMC_TIMING_HS400));
  ASSERT_NO_FATAL_FAILURES(mock_regs.VerifyAll());

  msdc_cfg.set_reg_value(0).set_card_ck_mode(MsdcCfg::kCardCkModeHs400);
  GetMockReg<MsdcCfg>(mock_regs)
      .ExpectRead(msdc_cfg.reg_value())
      .ExpectWrite()
      .ExpectRead()
      .ExpectWrite(msdc_cfg.set_card_ck_mode(MsdcCfg::kCardCkModeDiv).reg_value())
      .ExpectRead(msdc_cfg.set_card_ck_stable(1).reg_value());

  EXPECT_OK(sdmmc.SdmmcSetTiming(SDMMC_TIMING_HS200));
  mock_regs.VerifyAll();
}

TEST(SdmmcTest, SetTimingNoSdioUhs) {
  ddk_mock::MockMmioReg reg_array[kRegisterCount];
  ddk_mock::MockMmioRegRegion mock_regs(reg_array, sizeof(uint32_t), kRegisterCount);
  MtkSdmmcTest sdmmc(mock_regs, true);

  GetMockReg<MsdcCfg>(mock_regs).ReadReturns(MsdcCfg().set_card_ck_stable(1).reg_value());

  EXPECT_NOT_OK(sdmmc.SdmmcSetTiming(SDMMC_TIMING_DDR50));
  EXPECT_NOT_OK(sdmmc.SdmmcSetTiming(SDMMC_TIMING_SDR104));
  EXPECT_NOT_OK(sdmmc.SdmmcSetTiming(SDMMC_TIMING_SDR50));
  EXPECT_OK(sdmmc.SdmmcSetTiming(SDMMC_TIMING_HS));
  EXPECT_OK(sdmmc.SdmmcSetTiming(SDMMC_TIMING_LEGACY));
}

TEST(SdmmcTest, Request) {
  ddk_mock::MockMmioReg reg_array[kRegisterCount];
  ddk_mock::MockMmioRegRegion mock_regs(reg_array, sizeof(uint32_t), kRegisterCount);
  MtkSdmmcTest sdmmc(mock_regs);

  auto thread_ac = fbl::MakeAutoCall([&sdmmc] { sdmmc.StopIrqThread(); });

  // Set card_ck_stable so Init() can call SdmmcSetBusFreq() without hanging.
  GetMockReg<MsdcCfg>(mock_regs).ReadReturns(MsdcCfg().set_card_ck_stable(1).reg_value());
  sdmmc.Init();
  ASSERT_NO_FATAL_FAILURES(mock_regs.VerifyAll());

  // Command with no response.
  sdmmc_req_t req;
  memset(&req, 0, sizeof(req));
  req.cmd_idx = 50;
  req.cmd_flags = SDMMC_RESP_NONE | SDMMC_CMD_TYPE_NORMAL;
  req.arg = 0x1234abcd;
  req.status = 1;

  GetMockReg<MsdcInt>(mock_regs).ExpectRead(MsdcInt().set_cmd_ready(1).reg_value());
  GetMockReg<SdcArg>(mock_regs).ExpectWrite(req.arg);
  GetMockReg<SdcCmd>(mock_regs).ExpectWrite(req.cmd_idx);

  EXPECT_OK(sdmmc.SdmmcRequest(&req));
  EXPECT_OK(req.status);
  ASSERT_NO_FATAL_FAILURES(mock_regs.VerifyAll());

  // Command with response R1.
  req.cmd_idx = 19;
  req.cmd_flags = SDMMC_RESP_R1 | SDMMC_CMD_TYPE_NORMAL;
  req.arg = 0x55555555;
  req.status = 1;

  GetMockReg<MsdcInt>(mock_regs).ExpectRead(MsdcInt().set_cmd_ready(1).reg_value());
  GetMockReg<SdcArg>(mock_regs).ExpectWrite(req.arg);
  GetMockReg<SdcCmd>(mock_regs).ExpectWrite(
      SdcCmd().set_cmd(req.cmd_idx).set_resp_type(SdcCmd::kRespTypeR1).reg_value());
  GetMockReg<SdcResponse>(0, mock_regs).ExpectRead(0x1234abcd);

  EXPECT_OK(sdmmc.SdmmcRequest(&req));
  EXPECT_OK(req.status);
  EXPECT_EQ(req.response[0], 0x1234abcd);
  ASSERT_NO_FATAL_FAILURES(mock_regs.VerifyAll());

  // Command with response R2.
  req.cmd_idx = 22;
  req.cmd_flags = SDMMC_RESP_R2 | SDMMC_CMD_TYPE_NORMAL;
  req.arg = 0x12345678;
  req.status = 1;
  GetMockReg<MsdcInt>(mock_regs).ExpectRead(MsdcInt().set_cmd_ready(1).reg_value());
  GetMockReg<SdcArg>(mock_regs).ExpectWrite(req.arg);
  GetMockReg<SdcCmd>(mock_regs).ExpectWrite(
      SdcCmd().set_cmd(req.cmd_idx).set_resp_type(SdcCmd::kRespTypeR2).reg_value());
  GetMockReg<SdcResponse>(0, mock_regs).ExpectRead(0x0a0a0a0a);
  GetMockReg<SdcResponse>(1, mock_regs).ExpectRead(0x50505050);
  GetMockReg<SdcResponse>(2, mock_regs).ExpectRead(0x1234abcd);
  GetMockReg<SdcResponse>(3, mock_regs).ExpectRead(0xfedcba98);

  EXPECT_OK(sdmmc.SdmmcRequest(&req));
  EXPECT_OK(req.status);
  EXPECT_EQ(req.response[0], 0x0a0a0a0a);
  EXPECT_EQ(req.response[1], 0x50505050);
  EXPECT_EQ(req.response[2], 0x1234abcd);
  EXPECT_EQ(req.response[3], 0xfedcba98);
  mock_regs.VerifyAll();
}

TEST(SdmmcTest, ReadPolled) {
  ddk_mock::MockMmioReg reg_array[kRegisterCount];
  ddk_mock::MockMmioRegRegion mock_regs(reg_array, sizeof(uint32_t), kRegisterCount);
  MtkSdmmcTest sdmmc(mock_regs);

  auto thread_ac = fbl::MakeAutoCall([&sdmmc] { sdmmc.StopIrqThread(); });

  GetMockReg<MsdcCfg>(mock_regs).ReadReturns(MsdcCfg().set_card_ck_stable(1).reg_value());
  sdmmc.Init();
  ASSERT_NO_FATAL_FAILURES(mock_regs.VerifyAll());

  // Single block read.
  constexpr uint8_t single_block_data[16] = {0x12, 0xc2, 0x1c, 0x63, 0x54, 0x51, 0x7e, 0xf3,
                                             0x0a, 0x1b, 0xa5, 0x2a, 0xca, 0x23, 0x02, 0x82};
  uint8_t single_block_buf[sizeof(single_block_data)] = {0};

  sdmmc_req_t req;
  memset(&req, 0, sizeof(req));
  req.cmd_idx = 8;
  req.cmd_flags = SDMMC_RESP_R1 | SDMMC_CMD_TYPE_NORMAL | SDMMC_RESP_DATA_PRESENT | SDMMC_CMD_READ;
  req.arg = 0x72b2af17;
  req.status = 1;
  req.blockcount = 1;
  req.blocksize = sizeof(single_block_buf);
  req.virt_buffer = single_block_buf;
  req.virt_size = sizeof(single_block_buf);

  GetMockReg<MsdcInt>(mock_regs).ExpectRead(MsdcInt().set_cmd_ready(1).reg_value());
  GetMockReg<SdcArg>(mock_regs).ExpectWrite(req.arg);
  GetMockReg<SdcCmd>(mock_regs).ExpectWrite(SdcCmd()
                                                .set_cmd(req.cmd_idx)
                                                .set_resp_type(SdcCmd::kRespTypeR1)
                                                .set_block_type(SdcCmd::kBlockTypeSingle)
                                                .set_block_size(sizeof(single_block_buf))
                                                .reg_value());
  GetMockReg<SdcBlockNum>(mock_regs).ExpectWrite(1);
  GetMockReg<SdcResponse>(0, mock_regs).ExpectRead(0x80dcd8ff);
  GetMockReg<MsdcCfg>(mock_regs).ExpectWrite(MsdcCfg().set_pio_mode(1).reg_value());
  GetMockReg<MsdcFifoCs>(mock_regs).ExpectRead().ExpectWrite().ExpectRead(0).ExpectRead(
      MsdcFifoCs().set_rx_fifo_count(sizeof(single_block_data)).reg_value());

  for (size_t i = 0; i < sizeof(single_block_data); i++) {
    GetMockReg<MsdcRxData>(mock_regs).ExpectRead(single_block_data[i]);
  }

  EXPECT_OK(sdmmc.SdmmcRequest(&req));
  EXPECT_OK(req.status);
  EXPECT_EQ(req.response[0], 0x80dcd8ff);
  EXPECT_BYTES_EQ(single_block_data, single_block_buf, sizeof(single_block_data));
  ASSERT_NO_FATAL_FAILURES(mock_regs.VerifyAll());

  // Multi block read.
  constexpr uint8_t multi_block_data[64] = {
      0x99, 0x5b, 0xd9, 0x80, 0x35, 0x5e, 0xb9, 0x92, 0x07, 0xd2, 0x11, 0xd7, 0x72,
      0xb3, 0x61, 0x7b, 0xf8, 0x5a, 0x65, 0xf1, 0x43, 0x4d, 0x43, 0x78, 0x67, 0x67,
      0xd6, 0xd4, 0x3f, 0x0a, 0x1a, 0x93, 0x0f, 0x77, 0x71, 0x1b, 0xc6, 0x5a, 0x38,
      0xc0, 0xcd, 0x5f, 0x03, 0x63, 0x5f, 0xa6, 0x78, 0xb2, 0xf6, 0xdb, 0x00, 0x0e,
      0xd4, 0xf3, 0xe3, 0x69, 0xf2, 0x8e, 0x25, 0xaa, 0x6f, 0xbc, 0xe6, 0xba};
  uint8_t multi_block_buf[sizeof(multi_block_data)] = {0};

  req.cmd_idx = 36;
  req.cmd_flags = SDMMC_RESP_R1 | SDMMC_CMD_TYPE_NORMAL | SDMMC_RESP_DATA_PRESENT | SDMMC_CMD_READ |
                  SDMMC_CMD_MULTI_BLK;
  req.arg = 0x954887c8;
  req.status = 1;
  req.blockcount = 4;
  req.blocksize = sizeof(multi_block_buf) / 4;
  req.virt_buffer = multi_block_buf;
  req.virt_size = sizeof(multi_block_buf);

  GetMockReg<MsdcInt>(mock_regs).ExpectRead(MsdcInt().set_cmd_ready(1).reg_value());
  GetMockReg<SdcArg>(mock_regs).ExpectWrite(req.arg);
  GetMockReg<SdcCmd>(mock_regs).ExpectWrite(SdcCmd()
                                                .set_cmd(req.cmd_idx)
                                                .set_resp_type(SdcCmd::kRespTypeR1)
                                                .set_block_type(SdcCmd::kBlockTypeMulti)
                                                .set_block_size(sizeof(multi_block_buf) / 4)
                                                .set_auto_cmd(SdcCmd::kAutoCmd12)
                                                .reg_value());
  GetMockReg<SdcBlockNum>(mock_regs).ExpectWrite(4);
  GetMockReg<SdcResponse>(0, mock_regs).ExpectRead(0xaa30091e);
  GetMockReg<MsdcCfg>(mock_regs).ExpectWrite(MsdcCfg().set_pio_mode(1).reg_value());
  GetMockReg<MsdcFifoCs>(mock_regs)
      .ExpectRead()
      .ExpectWrite()
      .ExpectRead(0)
      .ExpectRead(MsdcFifoCs().set_rx_fifo_count(sizeof(multi_block_data) / 4).reg_value())
      .ExpectRead(0)
      .ExpectRead(0)
      .ExpectRead(MsdcFifoCs().set_rx_fifo_count(sizeof(multi_block_data) / 4).reg_value())
      .ExpectRead(MsdcFifoCs().set_rx_fifo_count(sizeof(multi_block_data) / 4).reg_value())
      .ExpectRead(0)
      .ExpectRead(0)
      .ExpectRead(0)
      .ExpectRead(MsdcFifoCs().set_rx_fifo_count(sizeof(multi_block_data) / 4).reg_value());

  for (size_t i = 0; i < sizeof(multi_block_data); i++) {
    GetMockReg<MsdcRxData>(mock_regs).ExpectRead(multi_block_data[i]);
  }

  EXPECT_OK(sdmmc.SdmmcRequest(&req));
  EXPECT_OK(req.status);
  EXPECT_EQ(req.response[0], 0xaa30091e);
  EXPECT_BYTES_EQ(multi_block_data, multi_block_buf, sizeof(multi_block_data));
  mock_regs.VerifyAll();
}

TEST(SdmmcTest, WritePolled) {
  ddk_mock::MockMmioReg reg_array[kRegisterCount];
  ddk_mock::MockMmioRegRegion mock_regs(reg_array, sizeof(uint32_t), kRegisterCount);
  MtkSdmmcTest sdmmc(mock_regs);

  auto thread_ac = fbl::MakeAutoCall([&sdmmc] { sdmmc.StopIrqThread(); });

  GetMockReg<MsdcCfg>(mock_regs).ReadReturns(MsdcCfg().set_card_ck_stable(1).reg_value());
  sdmmc.Init();
  mock_regs.VerifyAll();

  // Single block read.
  uint8_t single_block_data[16] = {0x12, 0xc2, 0x1c, 0x63, 0x54, 0x51, 0x7e, 0xf3,
                                   0x0a, 0x1b, 0xa5, 0x2a, 0xca, 0x23, 0x02, 0x82};

  sdmmc_req_t req;
  memset(&req, 0, sizeof(req));
  req.cmd_idx = 8;
  req.cmd_flags = SDMMC_RESP_R1 | SDMMC_CMD_TYPE_NORMAL | SDMMC_RESP_DATA_PRESENT;
  req.arg = 0x72b2af17;
  req.status = 1;
  req.blockcount = 1;
  req.blocksize = sizeof(single_block_data);
  req.virt_buffer = single_block_data;
  req.virt_size = sizeof(single_block_data);
  req.buf_offset = 0;

  GetMockReg<MsdcInt>(mock_regs).ExpectRead(MsdcInt().set_cmd_ready(1).reg_value());
  GetMockReg<SdcArg>(mock_regs).ExpectWrite(req.arg);
  GetMockReg<SdcCmd>(mock_regs).ExpectWrite(SdcCmd()
                                                .set_cmd(req.cmd_idx)
                                                .set_resp_type(SdcCmd::kRespTypeR1)
                                                .set_block_type(SdcCmd::kBlockTypeSingle)
                                                .set_block_size(sizeof(single_block_data))
                                                .set_write(1)
                                                .reg_value());
  GetMockReg<SdcBlockNum>(mock_regs).ExpectWrite(1);
  GetMockReg<SdcResponse>(0, mock_regs).ExpectRead(0x80dcd8ff);
  GetMockReg<MsdcCfg>(mock_regs).ExpectWrite(MsdcCfg().set_pio_mode(1).reg_value());
  GetMockReg<MsdcFifoCs>(mock_regs).ExpectRead().ExpectWrite().ExpectRead(0);

  for (size_t i = 0; i < sizeof(single_block_data); i++) {
    GetMockReg<MsdcTxData>(mock_regs).ExpectWrite(single_block_data[i]);
  }

  EXPECT_OK(sdmmc.SdmmcRequest(&req));
  EXPECT_OK(req.status);
  EXPECT_EQ(req.response[0], 0x80dcd8ff);
  ASSERT_NO_FATAL_FAILURES(mock_regs.VerifyAll());

  // Multi block read.
  uint8_t multi_block_data[64] = {0x99, 0x5b, 0xd9, 0x80, 0x35, 0x5e, 0xb9, 0x92, 0x07, 0xd2, 0x11,
                                  0xd7, 0x72, 0xb3, 0x61, 0x7b, 0xf8, 0x5a, 0x65, 0xf1, 0x43, 0x4d,
                                  0x43, 0x78, 0x67, 0x67, 0xd6, 0xd4, 0x3f, 0x0a, 0x1a, 0x93, 0x0f,
                                  0x77, 0x71, 0x1b, 0xc6, 0x5a, 0x38, 0xc0, 0xcd, 0x5f, 0x03, 0x63,
                                  0x5f, 0xa6, 0x78, 0xb2, 0xf6, 0xdb, 0x00, 0x0e, 0xd4, 0xf3, 0xe3,
                                  0x69, 0xf2, 0x8e, 0x25, 0xaa, 0x6f, 0xbc, 0xe6, 0xba};

  req.cmd_idx = 36;
  req.cmd_flags =
      SDMMC_RESP_R1 | SDMMC_CMD_TYPE_NORMAL | SDMMC_RESP_DATA_PRESENT | SDMMC_CMD_MULTI_BLK;
  req.arg = 0x954887c8;
  req.status = 1;
  req.blockcount = 4;
  req.blocksize = sizeof(multi_block_data) / 4;
  req.virt_buffer = multi_block_data;
  req.virt_size = sizeof(multi_block_data);

  GetMockReg<MsdcInt>(mock_regs).ExpectRead(MsdcInt().set_cmd_ready(1).reg_value());
  GetMockReg<SdcArg>(mock_regs).ExpectWrite(req.arg);
  GetMockReg<SdcCmd>(mock_regs).ExpectWrite(SdcCmd()
                                                .set_cmd(req.cmd_idx)
                                                .set_resp_type(SdcCmd::kRespTypeR1)
                                                .set_block_type(SdcCmd::kBlockTypeMulti)
                                                .set_block_size(sizeof(multi_block_data) / 4)
                                                .set_auto_cmd(SdcCmd::kAutoCmd12)
                                                .set_write(1)
                                                .reg_value());
  GetMockReg<SdcBlockNum>(mock_regs).ExpectWrite(4);
  GetMockReg<SdcResponse>(0, mock_regs).ExpectRead(0xaa30091e);
  GetMockReg<MsdcCfg>(mock_regs).ExpectWrite(MsdcCfg().set_pio_mode(1).reg_value());
  GetMockReg<MsdcFifoCs>(mock_regs).ExpectRead().ExpectWrite().ExpectRead(0);

  for (size_t i = 0; i < sizeof(multi_block_data); i++) {
    GetMockReg<MsdcTxData>(mock_regs).ExpectWrite(multi_block_data[i]);
  }

  EXPECT_OK(sdmmc.SdmmcRequest(&req));
  EXPECT_OK(req.status);
  EXPECT_EQ(req.response[0], 0xaa30091e);
  mock_regs.VerifyAll();
}

TEST(SdmmcTest, Protocol) {
  ddk_mock::MockMmioReg reg_array[kRegisterCount];
  ddk_mock::MockMmioRegRegion mock_regs(reg_array, sizeof(uint32_t), kRegisterCount);
  MtkSdmmcTest sdmmc(mock_regs);

  ASSERT_EQ(sdmmc.ddk_proto_id_, ZX_PROTOCOL_SDMMC);

  sdmmc_protocol_ops_t* ops = reinterpret_cast<sdmmc_protocol_ops_t*>(sdmmc.ddk_proto_ops_);
  ASSERT_NOT_NULL(ops);
  EXPECT_NOT_NULL(ops->host_info);
  EXPECT_NOT_NULL(ops->set_signal_voltage);
  EXPECT_NOT_NULL(ops->set_bus_width);
  EXPECT_NOT_NULL(ops->set_bus_freq);
  EXPECT_NOT_NULL(ops->set_timing);
  EXPECT_NOT_NULL(ops->hw_reset);
  EXPECT_NOT_NULL(ops->perform_tuning);
  EXPECT_NOT_NULL(ops->request);
}

TEST(SdmmcTest, IrqCallbackCalled) {
  ddk_mock::MockMmioReg reg_array[kRegisterCount];
  ddk_mock::MockMmioRegRegion mock_regs(reg_array, sizeof(uint32_t), kRegisterCount);
  MtkSdmmcTest sdmmc(mock_regs, true);

  ASSERT_OK(sdmmc.RegisterInBandInterrupt());

  auto thread_ac = fbl::MakeAutoCall([&sdmmc] { sdmmc.StopIrqThread(); });

  GetMockReg<MsdcCfg>(mock_regs).ReadReturns(MsdcCfg().set_card_ck_stable(1).reg_value());
  sdmmc.Init();
  mock_regs.VerifyAll();

  GetMockReg<MsdcInt>(mock_regs)
      .ExpectRead(MsdcInt().set_sdio_irq(1).reg_value())
      .ExpectRead(MsdcInt().set_sdio_irq(0).reg_value())
      .ExpectRead(MsdcInt().set_sdio_irq(1).reg_value())
      .ExpectRead(MsdcInt().set_sdio_irq(0).reg_value())
      .ExpectRead(MsdcInt().set_sdio_irq(1).reg_value());

  sdmmc.TriggerInterrupts(5);
  sdmmc.WaitForCallbacks(3);

  mock_regs.VerifyAll();
}

}  // namespace sdmmc
